-
Notifications
You must be signed in to change notification settings - Fork 683
Implement the basic Promise #1695
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
36d47bb
to
ae5ed3f
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
First round of review. Pretty good patch. But please be more careful about syntax rules. Just self-review your code.
*/ | ||
#ifndef OBJECT_VALUE | ||
# define OBJECT_VALUE(name, obj_builtin_id, prop_attributes) | ||
#endif /* !OBJECT_VALUE */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new model is calling this include pair:
#include "ecma-builtin-helpers-macro-defines.inc.h"
#include "ecma-builtin-helpers-macro-undefs.inc.h"
#include "ecma-promise-object.h" | ||
#include "ecma-try-catch-macro.h" | ||
#include "jrt.h" | ||
#include "jrt-libc-includes.h" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need all of these includes?
#include "ecma-helpers.h" | ||
#include "ecma-promise-object.h" | ||
#include "ecma-try-catch-macro.h" | ||
#include "jrt.h" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please drop unnecessary includes in all files.
* @return pointer to the PromiseReactionJob | ||
*/ | ||
static ecma_job_promise_reaction_t * | ||
create_promise_reaction_job (ecma_value_t reaction, /**< PromiseReaction */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These functions do not have ecma_ or ecma_job_ prefix. Even if they are static functions.
|
||
ecma_string_t *str_0 = ecma_new_ecma_string_from_uint32 (0); | ||
ecma_string_t *str_1 = ecma_new_ecma_string_from_uint32 (1); | ||
ecma_string_t *str_2 = ecma_new_ecma_string_from_uint32 (2); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do you use indices instead of names? Do they come from the standard?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In spec, they are "record" instead of object. I use object to represent them so that I can use of the current gc system instead of creating my own.
The relationship between spec definition and my design are list in the first post #1695 (comment)
Only internal code will use those properties. I choose indices to save space and time (create string from c char is less effecient) But I realized maybe I can use LIT_INTERNAL_MAGIC_STRING_XXX to represent the name. What do you think about it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, numbers are ok for me. But you can use symbolic strings if you want.
* @return ecma value | ||
*/ | ||
static ecma_value_t | ||
trigger_promise_reactions (ecma_collection_header_t *reactions, /**< lists of reactions */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ecma_ prefixes are missing
ecma_promise_object_t *promise_p = (ecma_promise_object_t *) obj_p; | ||
ecma_value_t ret = trigger_promise_reactions (promise_p->reject_reactions, reason); | ||
promise_p->reject_reactions = ecma_new_values_collection (NULL, 0, false); | ||
/* Free all fullfill_reactions */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Dot after sentence.
} /* fulfill_promise */ | ||
|
||
|
||
/** |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Two newlines.
*/ | ||
static ecma_value_t | ||
resolve_function_handler (const ecma_value_t function, | ||
const ecma_value_t this __attribute__((unused)), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't use GCC specific __attribute__ ((unused))
. Please use JERRY_UNUSED
* See also: ES2015 25.4.3.1 | ||
* | ||
* @return ecma value of the new promise object | ||
* Returned value must be freed with ecma_free_value |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
returned instead of Returned
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My bad, this should not be changed.
6b222bb
to
331716c
Compare
@akosthekiss Hi, the patch can pass all check in ubuntu, but failed in macOS. |
@akosthekiss I found the reason. I set no_pure functions as attr__pure by mistake. e.g.
it actually changes some values in the heap. In Ubuntu, the issue is not revealed. The GCC might do different optimizations for those 2 OS. |
@jiangzidong It's great that you've found the cause. Although I'm a bit confused as that function should count as pure (at least in release builds, when asserts are disabled), or do I get something wrong? BTW, on macOS, gcc is not gcc but a wrapper around clang/llvm. So they may easily do things differently. |
{ | ||
ecma_value_t reaction; | ||
ecma_value_t argument; | ||
} ecma_job_promise_reaction_t; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
missing comments
ecma_value_t promise; | ||
ecma_value_t thenable; | ||
ecma_value_t then; | ||
} ecma_job_promise_resolve_thenable_t; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ditto
/** | ||
* Check if alreadyResolved is true. | ||
* | ||
* @return bool |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add a better description to the return value.
* | ||
* See also: ES2015 25.4.1.8 | ||
* | ||
* @return ecma value |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ditto
* | ||
* See also: ES2015 25.4.1.7 | ||
* | ||
* @return ecma value |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ditto
ecma_promise_reject_handler (const ecma_value_t function, | ||
const ecma_value_t this, | ||
const ecma_value_t argv[], | ||
const ecma_length_t argc) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
missing comments
ecma_promise_resolve_handler (const ecma_value_t function, | ||
const ecma_value_t this, | ||
const ecma_value_t argv[], | ||
const ecma_length_t argc) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ditto
ecma_promise_executor_handler (const ecma_value_t function, | ||
const ecma_value_t this, | ||
const ecma_value_t argv[], | ||
const ecma_length_t argc) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ditto
if (resolve != ecma_make_simple_value (ECMA_SIMPLE_VALUE_UNDEFINED)) | ||
{ | ||
ret = ecma_raise_type_error (ECMA_ERR_MSG ("'resolve' function should be undefined.")); | ||
goto end_of_executor_function; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not completely against goto
statements, but I think it can be avoided here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, I can remove this goto
.
What about the goto
in ecma_promise_resolve_handler
? I think that one is needed, otherwise the code will be quite 'dirty'
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I saw that too, but I agree. The code would be much less readable if you eliminate that one.
} ecma_promise_object_t; | ||
|
||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Too many empty lines. One is enough :)
5b6e6e1
to
7466381
Compare
@akosthekiss Sorry that I deleted my last comment by accident.
I don't think the above function is a pure function. It has side affect, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Next round of review, questions.
jerry-core/ecma/base/ecma-gc.c
Outdated
if (ext_object_p->u.class_prop.class_id == LIT_MAGIC_STRING_PROMISE_UL) | ||
{ | ||
/* mark promise result */ | ||
ecma_value_t result = ext_object_p->u.class_prop.u.value; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mark promise result.
jerry-core/ecma/base/ecma-gc.c
Outdated
{ | ||
ecma_gc_set_object_visited (ecma_get_object_from_value (result), true); | ||
} | ||
/* mark all reactions */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mark all reactions.
And add a newline before it.
|
||
/* 4. */ | ||
ecma_object_t *executor_p; | ||
executor_p = ecma_op_create_external_function_object ((ecma_external_pointer_t) ecma_promise_executor_handler); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems to be a static function which is always the same. If this is a private opration, why do we need a JS representation for it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I should directly call the executor instead of creating js function.
ecma_job_promise_reaction_t *job_p; | ||
job_p = (ecma_job_promise_reaction_t *) jmem_heap_alloc_block (sizeof (ecma_job_promise_reaction_t)); | ||
|
||
job_p->reaction = ecma_copy_value (reaction); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the code below it seems reaction is always an object. We should add an assert here.
if (ecma_is_value_false (handler) || ECMA_IS_VALUE_ERROR (handler_result)) | ||
{ | ||
/* str '2' indicates [[Reject]] of Capability */ | ||
handler_result = handler_result & ~ECMA_VALUE_ERROR_FLAG; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ecma_get_value_from_error_value
should be called here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! I didn't noticed this api
|
||
ecma_promise_object_t *promise_p = (ecma_promise_object_t *) obj_p; | ||
ecma_promise_trigger_reactions (promise_p->reject_reactions, reason); | ||
promise_p->reject_reactions = ecma_new_values_collection (NULL, 0, false); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't this be a NULL value?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it can. But if so, when we add reactions to the list, we have to create a collection when the value is NULL
ecma_free_values_collection (promise_p->fulfill_reactions, false); | ||
promise_p->fulfill_reactions = ecma_new_values_collection (NULL, 0, false); | ||
|
||
ecma_promise_set_state (obj_p, ECMA_PROMISE_STATE_REJECTED); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Qestion: is it possible that a callback triggers the same promise again? If so, this state set must occur after the assertion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No. But I will put the set after the assertion to reduce misunderstanding.
ecma_free_values_collection (promise_p->reject_reactions, false); | ||
promise_p->reject_reactions = ecma_new_values_collection (NULL, 0, false); | ||
|
||
ecma_promise_set_state (obj_p, ECMA_PROMISE_STATE_FULFILLED); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same comments as above.
/* 1. */ | ||
JERRY_ASSERT (ecma_is_promise (ecma_get_object_from_value (promise))); | ||
/* 3. */ | ||
ecma_value_t already_resolved = ecma_op_object_get (function_p, str_1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't really understand this part. The function_p is a public function where we define a "1" property? What ensures that we can define this property?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I should use LIT_INTERNAL_MAGIC_STRING_XXX here to prevent outer user from getting access to the internal property.
ecma_string_t *str_1 = ecma_new_ecma_string_from_uint32 (1); | ||
/* 2. */ | ||
ecma_object_t *resolve_p; | ||
resolve_p = ecma_op_create_external_function_object ((ecma_external_pointer_t) ecma_promise_resolve_handler); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is again seems to be a case where we have fixed internal handlers, there is no point creating JS functions for them, just call them directly whenever they needed. Creating JS functions is costly, and calling them is slower than natively calling them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
1). resolve_handler and reject_handler are not used internally, it will be used as args of executor, which is passed by user to create a Promise. So I think it is needed to create JS function for ecma_promise_resolve_handler
and ecma_promise_reject_handler
.
e.g.
var a = new Promise (function(f, r) // here f is the resolve function and r is the reject function
{
xxxx,
f(); // user control the logic of calling f or r
});
2). you're right in your last comment, because reject and resolve will be passed to the user's function, we cannot define a normal string property for function_p
. I will use LIT_INTERNAL_MAGIC_STRING_XXX as the property name, so that outer user can not get access to the property.
3).While the creation of ecma_promise_executor_handler
is not necessary, and I will change it.
jerry-core/ecma/base/ecma-gc.c
Outdated
@@ -179,7 +182,15 @@ ecma_gc_mark_property (ecma_property_pair_t *property_pair_p, /**< property pair | |||
if (ECMA_PROPERTY_GET_NAME_TYPE (property) == ECMA_STRING_CONTAINER_MAGIC_STRING | |||
&& property_pair_p->names_cp[index] >= LIT_NON_INTERNAL_MAGIC_STRING__COUNT) | |||
{ | |||
#ifndef CONFIG_DISABLE_ES2015_PROMISE_BUILTIN | |||
if (property_pair_p->names_cp[index] < LIT_INTERNAL_MAGIC_STRING_PROMISE) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to do this better, since this case would be too specialized for promises. We need to introduce a constant for those internal strings, which property value must not be marked. Promise magic string ID should be lower than this constant.
jerry-core/ecma/base/ecma-gc.c
Outdated
|
||
if (ext_object_p->u.class_prop.class_id == LIT_MAGIC_STRING_PROMISE_UL) | ||
{ | ||
/* Mark promise result */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The dot is still missing after the sentences.
|
||
if (arguments_list_len == 0 || !ecma_op_is_callable (arguments_list_p[0])) | ||
{ | ||
return ecma_raise_type_error (ECMA_ERR_MSG ("first parameter must be callable.")); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the ECMA_ERR_MSG above you started the sentence with a capital letter, here with a small letter. Please use only one style, which is used in the other part of the project.
ecma_free_value (reject); | ||
} | ||
/* 8 */ | ||
else |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't use comment before else, put it inside the {} block.
|
||
JERRY_ASSERT (ext_object_p->u.class_prop.class_id == LIT_MAGIC_STRING_BOOLEAN_UL); | ||
|
||
return ext_object_p->u.class_prop.u.value == ecma_make_boolean_value (true); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are these functions user supplied?
*/ | ||
static void | ||
ecma_reject_promise (ecma_value_t promise, /**< promise */ | ||
ecma_value_t reason) /**< reason for reject */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indentation.
*/ | ||
static void | ||
ecma_fulfill_promise (ecma_value_t promise, /**< promise */ | ||
ecma_value_t value) /**< fulfilled value */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indentation.
ecma_init_ecma_magic_string (&str_already_resolved, LIT_INTERNAL_MAGIC_STRING_ALREADY_RESOLVED); | ||
/* 2. */ | ||
ecma_object_t *resolve_p; | ||
resolve_p = ecma_op_create_external_function_object ((ecma_external_pointer_t) ecma_promise_resolve_handler); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are these functions can be called by users? They seem internal based on their name. I would prefer just directly call them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please see my comments below
This pair of functions (resolve function and reject function) are not supplied by user, but will pass to user as arguments. JS engine prepared/implemented these 2 functions, and let users to use them. For JS users, they are standard js functions. for example
|
Updated for the 2nd review |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
*/ | ||
static void | ||
ecma_free_promise_resolve_thenable_job (ecma_job_promise_resolve_thenable_t *job_p) /**< points to the | ||
* PromiseResolveThenableJob */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wrong indentation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
@@ -142,6 +142,8 @@ double jerry_port_get_current_time (void); | |||
|
|||
#ifndef CONFIG_DISABLE_ES2015_PROMISE_BUILTIN | |||
|
|||
#define JERRY_PORT_ENABLE_JOBQUEUE |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we still need this define?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The marco is for main-unix.c, since I added the jobqueue related code in it. Also the jerry-port-default-jobqueue.c used the macro.
Those two files are not in the jerry-core, so the profile config doesn't work on them
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does it make sense, can I merge the patch? @zherczeg
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, lets merge it, and we can address such things later.
Implement the Promise Constructor and routine: 'then' JerryScript-DCO-1.0-Signed-off-by: Zidong Jiang [email protected]
main-unix
for both file mode and repl modeIn the spec of Promise, there are PromiseCapability Record and PromiseReaction Record. I use ecma_object_t to represent them cause it can reuse the current GC system
In the record object, I use UINT32 type string to represent each property names. I choose uint32 string for saving space. Or should I use
LIT_INTERNAL_MAGIC_STRING_XXX
instead?Data Structure for Promise
Promise
In Spec:
In Jerry
And it is a CLASS type object, and its
ext_object_p->u.class_prop.u.value
stores the promiseResultEach elements in
fulfill_reactions
andreject_reactions
are also ecma_object.Those internal property are not accessible by js code. so js users will not read/write those values via getter/setter of promise object
PromiseReaction Record
It is the elements of promise.[[PromiseFulfillReactions]] and [[PromiseRejectReactions]]
In Spec:
In Jerry:
an GENERAL_TYPE object with following properties:
"0": the PromiseCapability object
"1": handler, could be function or boolean value. If it is boolean, true means "identity" and false means "thrower"
PromiseCapability Records
In spec:
In Jerry:
Similar with PromiseReaction , a general object to represent, with uint32_string properties
"0": the promise object
"1": resolve function
"2": reject function